package com.dayu.finecomm.handler.tpacket;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.dayu.finecomm.mongo.pojo.Packet;
import com.dayu.finecomm.mongo.pojo.StEfficiency;
import com.dayu.finecomm.mongo.pojo.StSwitch;
import com.dayu.finecomm.mongo.service.MongoDAO;
import com.dayu.finecomm.protocol.SpecialCareChannelInboundAdapter;
import com.dayu.finecomm.protocol.pojo.TPacket;
import com.dayu.finecomm.protocol.pojo.TSensor;
import com.dayu.finecomm.protocol.pojo.TSwitch;
import com.dayu.finecomm.utils.BsonTool;
import com.dayu.finecomm.utils.RedisCacheManager;
import com.mongodb.BasicDBObject;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;

/**
 * @ClassName: TPacketStoreHandler
 * @Description:缓存报文到Redis或数据库
 * @author: 戴卫国
 * @date: 2018年4月4日 上午11:48:15
 * 
 * @Copyright: 2018 dayu Inc. All rights reserved.
 */
@Component
public final class TPacketStoreHandler extends SpecialCareChannelInboundAdapter<TPacket> {

	static Logger LOGGER = LoggerFactory.getLogger(TPacketStoreHandler.class);

	Bootstrap BOOTSTRAP;
	Bootstrap BOOTSTRAPUDP;

	@Autowired
	private RedisCacheManager redisCache;

	@Resource
	MongoDAO dao;

	@Value("${mongodb.collection.packet.enabled}")
	boolean save_packet;

	@Value("${mongodb.collection.histroy.enabled}")
	boolean save_histroy;

	@Value("${mongodb.collection.packet.savedays}")
	int savedays_packet;

	@Value("${mongodb.collection.histroy.savedays}")
	int savedays_histroy;

	public static TPacketStoreHandler handler; // 关键2

	private boolean needten = false;

	HashedWheelTimer timer = new HashedWheelTimer();

	public TPacketStoreHandler() {
	}

	// 关键3
	@PostConstruct
	public void init() {
		handler = this;
		handler.redisCache = this.redisCache;
		handler.savedays_packet = this.savedays_packet;
		handler.savedays_histroy = this.savedays_histroy;
		handler.save_packet = this.save_packet;
		handler.save_histroy = this.save_histroy;

		handler.dao = this.dao;
		clearOutimeHis();
	}

	private void clearOutimeHis() {
		// 清除过期的报文数据
		handler.dao.packet.remove(new BasicDBObject().append("t",
				new BasicDBObject().append("$lt", new Date().getTime() / 1000 - 86400 * handler.savedays_packet)));
		// 清除过期的历史数据
		handler.dao.histroy.remove(new BasicDBObject().append("time",
				new BasicDBObject().append("$lt", new Date().getTime() / 1000 - 86400 * handler.savedays_histroy)));

		timer.newTimeout(new TimerTask() {
			public void run(Timeout timeout) throws Exception {
				clearOutimeHis();
			}
		}, 1, TimeUnit.DAYS);
	}

	@Override
	public void channelRead0(ChannelHandlerContext ctx, final TPacket msg) throws Exception {
		LOGGER.info("TPacketStoreHandler - channelRead0 - start");
		List<TPacket> removed = new ArrayList<>();
		for (TPacket p : msg.packets) {
			TSwitch snv = parseCanStationData((String) handler.redisCache.hget(msg.sn, p.sn));
			int perStatus = snv.status;
			if ((short) p.items("S")[0].val == snv.serial) { // 序列号一样过滤
				removed.add(p);
				continue;
			}
			for (TSensor s : p.sensors) {
				if (s.channel.equals("V")) {// 实时转速
					if (snv.value != (int) s.val) {
						snv.value = (int) s.val;
					}
				} else if (s.channel.equals("K")) {// 实时开关量
					if ((short) s.val == 0) {
						continue;
					}

					// 转速大于300的认为是运行的
					if ((short) s.val != 30 && snv.value >= 300) {
						s.val = (short)30;
					}

					// 先判断前后时间是否大于10分钟，大于10分钟的数据，添加停止信号
					/*
					if (needten && s.timestamp - snv.time > 600 && snv.time > 0) {
						// 插入一个未知停的状态包
						// 1.计算插入的指针

						// 保存缓存在Radis中的状态
						appandSwitch(msg.sn, p.sn, snv, snv.pertime, perStatus);
						appandHistroy(msg.sn, p.sn, snv);

						long total = s.timestamp - snv.time - 600;
						doStopLongTime201(msg.sn, p.sn, snv, total, snv.time, perStatus);

						// 2.插入一个未知停的记录
						snv.status = 201;
						snv.pertime = snv.time;// 第一次记录前一个时间
						snv.time = s.timestamp - 600;
						appandSwitch(msg.sn, p.sn, snv, snv.pertime, perStatus);
						// 修改上一次的状态和时间
						perStatus = snv.status;
						snv.pertime = snv.time;

					}
					*/

					// 存效率和停次数
					if ((short) s.val == 30) {
						doRealTime(msg.sn, p.sn, snv, s);
					} else {
						doStopRealTime(msg.sn, p.sn, snv, s);
					}

					// 存入实时状态
					if (snv.status != (short) s.val) {
						snv.status = (short) s.val;
						snv.time = s.timestamp;
						appandSwitch(msg.sn, p.sn, snv, snv.pertime, perStatus);
						snv.pertime = snv.time;// 状态开始时间计为当前时间
					}
					if (snv.pertime == 0) {
						snv.pertime = snv.time;
					}
					snv.time = s.timestamp;
					appandHistroy(msg.sn, p.sn, snv);
				} else if (s.channel.equals("H") && snv.time > 0) {// 历史报文
					@SuppressWarnings("unchecked")
					Map<String, List<Integer>> h = (Map<String, List<Integer>>) s.val;
					List<Integer> l = h.get("L");
					List<Integer> v = h.get("V");
					for (int i = 0; i < l.size(); i++) {
						if (v.get(i) > 0) {
							// 处理这段时间内的运行
							doRunLongTime100(msg.sn, p.sn, snv, l.get(i), v.get(i));

							// 插入到switch表
							if (snv.status != 100) {
								// 正常情况下延续上一个状态，肯定是运行，如果当前不是运行，那么直接插入一个运行，覆盖掉原来的停止
								snv.status = 100;
								snv.value = v.get(i);
								appandSwitch(msg.sn, p.sn, snv, snv.pertime, perStatus);
								appandHistroy(msg.sn, p.sn, snv);
							}
							perStatus = snv.status;
							snv.status = 100;
							snv.value = v.get(i);
						} else {
							// 处理这段时间内的停止
							doStopLongTime101(msg.sn, p.sn, snv, l.get(i));

							// 插入到switch表
							if (snv.status != 101) {
								// 正常情况下延续上一个状态，肯定是运行，如果当前不是运行，那么直接插入一个运行，覆盖掉原来的停止
								snv.status = 101;
								snv.value = v.get(i);
								appandSwitch(msg.sn, p.sn, snv, snv.pertime, perStatus);
								appandHistroy(msg.sn, p.sn, snv);
							}
							perStatus = snv.status;
							snv.status = 101;
							snv.value = 0;
						}
						snv.pertime = snv.time;
						snv.time = snv.time + l.get(i);
					}
				} else if (s.channel.equals("S")) {
					snv.serial = (short) s.val;
				}
			}
			if (snv.time > 0) {
				handler.redisCache.hset(msg.sn, p.sn, JSON.toJSONString(snv));
			}
		}
		msg.packets.removeAll(removed);
		appandPacket(ctx, msg);
		ctx.fireChannelRead(msg);
		LOGGER.info("TPacketStoreHandler - channelRead0 - end");
	}

	/**
	 * 实时运行（实时报文之间只能是10分钟以内，肯定在一个缓存格子中）
	 */
	private void doRealTime(String sn1, String sn2, TSwitch snv, TSensor s) {

		int index = findIndex(s.timestamp);
		long zero = getStartTimeOfDay();

		if (index == snv.efficiencyIndex) {
			snv.efficiency[index] += (snv.time > 0 ? (s.timestamp - snv.time) : 0);
		} else {
			long per = 0;
			// 新的一天
			if (index == 0) {
				snv.efficiency[snv.efficiencyIndex] += (snv.time > 0 ? (zero - snv.time) : 0);
				snv.speed[snv.efficiencyIndex] = (int) (snv.speed[snv.efficiencyIndex] < snv.value ? snv.value
						: snv.speed[snv.efficiencyIndex]);
				appandHistroyEfficiency(sn1, sn2, zero - 24 * 3600, snv);
				per = zero;
			} else {
				per = zero + 1800 * index;
				snv.efficiency[snv.efficiencyIndex] += (snv.time > 0 ? (per - snv.time) : 0);
			}
			// 重置运行时间为当前时间减去空格开始时间
			snv.efficiency[index] = (int) (s.timestamp - per);
			snv.speed[index] = snv.value;
			snv.stoptimes_29[index] = 0;
			snv.stoptimes_27[index] = 0;
			snv.stoptimes_23[index] = 0;
			snv.stoptimes_31[index] = 0;
			snv.stoptimes_101[index] = 0;
			snv.stoptimes_201[index] = 0;
		}
		snv.efficiencyIndex = index;
	}

	/**
	 * 实时停止（实时报文之间只能是10分钟以内，肯定在一个缓存格子中）
	 */
	private void doStopRealTime(String sn1, String sn2, TSwitch snv, TSensor s) {
		int index = findIndex(s.timestamp);
		long zero = getStartTimeOfDay();

		if (index != snv.efficiencyIndex) {
			// 新的一天
			if (index == 0) {
				appandHistroyEfficiency(sn1, sn2, zero - 24 * 3600, snv);// 将24小时的数据存入数据库，时间是上一日的零点
			}

			snv.stoptimes_29[index] = 0;
			snv.stoptimes_27[index] = 0;
			snv.stoptimes_23[index] = 0;
			snv.stoptimes_31[index] = 0;
			snv.stoptimes_101[index] = 0;
			snv.stoptimes_201[index] = 0;
			snv.efficiency[index] = 0;
			snv.speed[index] = 0;
		}

		if (snv.status != (short) s.val) {
			switch ((short) s.val) {
			case 29:// 满停 1D
				snv.stoptimes_29[index] += 1;
				break;
			case 27:// 耳停 1B
				snv.stoptimes_27[index] += 1;
				break;
			case 23:// 违停 17
				snv.stoptimes_23[index] += 1;
				break;
			case 31:// 其他 1F
				snv.stoptimes_31[index] += 1;
				break;
			}
		}

		snv.efficiencyIndex = index;
	}

	/**
	 * 处理长时间运行的补包
	 * 
	 * @param sn1   集中器编号
	 * @param sn2   机器编号
	 * @param snv   缓存信息
	 * @param total 运行时长（秒）
	 * @param speed 运行速度（转）
	 */
	private void doRunLongTime100(String sn1, String sn2, TSwitch snv, long total, int speed) {
		long zero = getStartTimeOfDay(snv.time);
		long startTime = snv.time - zero;
		// 剩余时间大于半小时或者跨格了
		LOGGER.info(sn1 + ":" + sn2 + "  doRunLongTime100 efficiencyIndex:" + snv.efficiencyIndex + " startTime:"
				+ startTime + " total:" + total + " condition:"
				+ ((total > 1800 || 1800 * (snv.efficiencyIndex + 1) - startTime < total)));
		while (total > 1800 || 1800 * (snv.efficiencyIndex + 1) - startTime < total) {
			long runTime = (1800 * (snv.efficiencyIndex + 1) - startTime);
			LOGGER.info(sn1 + ":" + sn2 + "  doRunLongTime100 runTime:" + runTime + "  efficiencyIndex:"
					+ snv.efficiencyIndex);
			snv.efficiency[snv.efficiencyIndex] += runTime;
			snv.speed[snv.efficiencyIndex] = speed;

			if (snv.efficiencyIndex == 47) {// 将24小时的数据存入数据库
				appandHistroyEfficiency(sn1, sn2, zero - 24 * 3600, snv);
				// 重置为0
				snv.efficiencyIndex = 0;
				startTime = 0;
			} else {
				// 跨度加1
				snv.efficiencyIndex += 1;
			}
			// 重置运行时间
			snv.efficiency[snv.efficiencyIndex] = 0;
			// 重置停止次数
			snv.stoptimes_29[snv.efficiencyIndex] = 0;
			snv.stoptimes_27[snv.efficiencyIndex] = 0;
			snv.stoptimes_23[snv.efficiencyIndex] = 0;
			snv.stoptimes_31[snv.efficiencyIndex] = 0;
			snv.stoptimes_101[snv.efficiencyIndex] = 0;
			snv.stoptimes_201[snv.efficiencyIndex] = 0;
			// 重置速度
			snv.speed[snv.efficiencyIndex] = 0;

			startTime = 1800 * snv.efficiencyIndex;
			total -= runTime;
		}
		// 剩余时间在一格内
		snv.efficiency[snv.efficiencyIndex] += total;
		LOGGER.info(
				sn1 + ":" + sn2 + "  doRunLongTime100 runTime:" + total + "  efficiencyIndex:" + snv.efficiencyIndex);
	}

	private void doStopLongTime101(String sn1, String sn2, TSwitch snv, long total) {
		// 处理这段时间内的运行
		long zero = getStartTimeOfDay(snv.time);
		long startTime = snv.time - zero;
		// 剩余时间大于半小时或者跨格了
		LOGGER.info(sn1 + ":" + sn2 + "  doStopLongTime101 efficiencyIndex:" + snv.efficiencyIndex + " startTime:"
				+ startTime + " total:" + total + " condition:"
				+ ((total > 1800 || 1800 * (snv.efficiencyIndex + 1) - startTime < total)));
		while (total > 1800 || 1800 * (snv.efficiencyIndex + 1) - startTime < total) {
			long stopTime = (1800 * (snv.efficiencyIndex + 1) - startTime);
			LOGGER.info(sn1 + ":" + sn2 + "  doStopLongTime101 runTime:" + stopTime + "  efficiencyIndex:"
					+ snv.efficiencyIndex);
			snv.stoptimes_101[snv.efficiencyIndex] += 1;
			snv.speed[snv.efficiencyIndex] = 0;

			if (snv.efficiencyIndex == 47) {// 将24小时的数据存入数据库
				appandHistroyEfficiency(sn1, sn2, zero - 24 * 3600, snv);
				// 重置为0
				snv.efficiencyIndex = 0;
				startTime = 0;
			} else {
				// 跨度加1
				snv.efficiencyIndex += 1;
			}
			// 重置运行时间
			snv.efficiency[snv.efficiencyIndex] = 0;
			// 其他停止位0
			snv.stoptimes_29[snv.efficiencyIndex] = 0;
			snv.stoptimes_27[snv.efficiencyIndex] = 0;
			snv.stoptimes_23[snv.efficiencyIndex] = 0;
			snv.stoptimes_31[snv.efficiencyIndex] = 0;
			snv.stoptimes_101[snv.efficiencyIndex] = -1;
			snv.stoptimes_201[snv.efficiencyIndex] = 0;
			// 重置速度
			snv.speed[snv.efficiencyIndex] = 0;

			startTime = 1800 * snv.efficiencyIndex;
			total -= stopTime;
		}
		// 剩余时间在一格内
		snv.stoptimes_101[snv.efficiencyIndex] += 1;
	}

	/**
	 * 未知停
	 */
	private void doStopLongTime201(String sn1, String sn2, TSwitch snv, long total, long perTime, int perStatus) {
		// 处理这段时间内的运行
		long zero = getStartTimeOfDay(snv.time);
		long startTime = snv.time - zero;
		// 剩余时间大于半小时或者跨格了
		while (total > 1800 || 1800 * (snv.efficiencyIndex + 1) - startTime < total) {
			long topTime = (1800 * (snv.efficiencyIndex + 1) - startTime);
			snv.stoptimes_101[snv.efficiencyIndex] += 1;
			snv.speed[snv.efficiencyIndex] = 0;

			if (snv.efficiencyIndex == 47) {// 将24小时的数据存入数据库
				appandHistroyEfficiency(sn1, sn2, zero - 24 * 3600, snv);
				// 重置为0
				snv.efficiencyIndex = 0;
				snv.stoptimes_201[snv.efficiencyIndex] = -1;
				startTime = 0;
			} else {
				// 跨度加1
				snv.efficiencyIndex += 1;
				snv.stoptimes_201[snv.efficiencyIndex] = -1;
			}
			// 重置运行时间
			snv.efficiency[snv.efficiencyIndex] = 0;
			// 其他停止位0
			snv.stoptimes_29[snv.efficiencyIndex] = 0;
			snv.stoptimes_27[snv.efficiencyIndex] = 0;
			snv.stoptimes_23[snv.efficiencyIndex] = 0;
			snv.stoptimes_31[snv.efficiencyIndex] = 0;
			snv.stoptimes_101[snv.efficiencyIndex] = 0;
			snv.speed[snv.efficiencyIndex] = 0;

			startTime = 1800 * snv.efficiencyIndex;
			total -= topTime;
		}
		// 剩余时间在一格内
		snv.stoptimes_201[snv.efficiencyIndex] += 1;
	}

	/**
	 * @Title: 数据库存切换记录
	 * @Description: TODO
	 * @param: 集中器编号 sn1
	 * @param: 机器编号 sn2
	 * @param: 当前转速 v
	 * @param: 当前状态 k
	 * @param: 当前时间 time
	 */
	private void appandSwitch(String sn1, String sn2, TSwitch s, long perTime, int perStatus) {
		StSwitch sw = new StSwitch();
		sw.setMidWayNo(sn1);
		sw.setStationNo(sn2);
		sw.setValue(s.getValue());
		sw.setValue1(s.getValue1());
		sw.setTime(s.getTime().intValue());
		sw.setStatus(s.getStatus());
		sw.setPerTime(perTime);
		sw.setPerStatus(perStatus);
		handler.dao.stswitch.insert(sw);
	}

	/**
	 * 保存历史记录
	 * 
	 * @Title: appandHistroy
	 * @Description: TODO
	 * @param: 集中器编号 sn1
	 * @param: 机器编号 sn2
	 * @param: 切换状态 s
	 */
	private void appandHistroy(String sn1, String sn2, TSwitch s) {
		if (handler.save_histroy) {
			StSwitch sw = new StSwitch();
			sw.setMidWayNo(sn1);
			sw.setStationNo(sn2);
			sw.setValue(s.getValue());
			sw.setValue1(s.getValue1());
			sw.setTime(s.getTime().intValue());
			sw.setStatus(s.getStatus());
			handler.dao.histroy.insert(sw);
		}
	}

	/**
	 * 保存1天的历史效率
	 * 
	 * @Title: appandHistroyEfficiency
	 * @Description: TODO
	 * @param: 集中器编号 sn1
	 * @param: 机器编号 sn2
	 * @param: @param time 时间
	 * @param: @param s 切换状态
	 */
	private void appandHistroyEfficiency(String sn1, String sn2, long time, TSwitch s) {
		StEfficiency se = new StEfficiency();
		se.setMidWayNo(sn1);
		se.setStationNo(sn2);
		se.setTime((int) time);
		se.setEfficiency(s.getEfficiency());
		se.setStopTimes29(s.getStoptimes_29());
		se.setStopTimes27(s.getStoptimes_27());
		se.setStopTimes23(s.getStoptimes_23());
		se.setStopTimes31(s.getStoptimes_31());
		se.setStopTimes101(s.getStoptimes_101());
		se.setStopTimes201(s.getStoptimes_201());
		se.setSpeed(s.getSpeed());
		se.setCreateTime(System.currentTimeMillis() / 1000);
		handler.dao.histroy_efficiency.insert(se);

	}

	private void appandPacket(ChannelHandlerContext ctx, TPacket p) {
		if (handler.save_packet) {
			Packet pc = new Packet();
			pc.setValue(BsonTool.objectToBsonValue(p));
			pc.setT(p.timestamp.intValue());
			pc.setSource(ctx.channel().remoteAddress().toString());
			handler.dao.packet.insert(pc);
		}
	}

	public static class TPacketPostData {

		public String method;

		public String payload;

		public TPacketPostData() {
		}

		public TPacketPostData(String method, String payload) {

			this.method = method;
			this.payload = payload;
		}

	}

	private TSwitch parseCanStationData(String s) {
		if (s != null) {
			return JSON.parseObject(s, TSwitch.class);
		} else {
			return new TSwitch();
		}

	}

	/**
	 * 时间戳转日期
	 * 
	 * @param ms
	 * @return
	 */
	private Date transForDate(Long ms) {
		if (ms == null) {
			ms = 0L;
		}
		long msl = ms * 1000;
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Date temp = null;
		if (ms != null) {
			try {
				String str = sdf.format(msl);
				temp = sdf.parse(str);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return temp;
	}

	private int findIndex(Long ms) {
		Date date = transForDate(ms);
		return (date.getHours() * 60 + date.getMinutes()) / 30;
	}

	private long getStartTimeOfDay() {
		TimeZone curTimeZone = TimeZone.getTimeZone("GMT+8");
		Calendar calendar = Calendar.getInstance(curTimeZone);
		calendar.setTimeInMillis(System.currentTimeMillis());
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar.getTimeInMillis() / 1000;
	}

	private static long getStartTimeOfDay(long now) {
		TimeZone curTimeZone = TimeZone.getTimeZone("GMT+8");
		Calendar calendar = Calendar.getInstance(curTimeZone);
		calendar.setTimeInMillis(now * 1000);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar.getTimeInMillis() / 1000;
	}
}